package first.group.ssh.readBuffer;

import first.group.ssh.DataConsumer;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.function.Function;

public class ReadBuffer implements DataConsumer {
    private final ByteBuffer buffer;
    private int pos = 0, size = 0, mark = 0, consumed = 0;

    public ReadBuffer() {
        this(65536);
    }

    public ReadBuffer(int capacity) {
        this.buffer = ByteBuffer.allocate(capacity);
    }
    
    public int getCapacity() {
        return buffer.capacity();
    }
    
    @Override
    public int fillData(ReadableByteChannel channel) throws IOException {
        buffer.position(size);
        var len = channel.read(buffer);
        if (len < 0) {
            throw new EOFException();
        }
        buffer.position(0);
        size += len;
        return len;
    }
    
    @Override
    public int fillData(byte[] data, int off, int len) {
        var readLen = Math.min(getRemaining(), len);
        buffer.position(size);
        buffer.put(data, off, readLen);
        buffer.position(0);
        size += len;
        return len;
    }
    
    public void consume() {
        for (int i = 0; i < size - pos; i++) {
            buffer.put(i, buffer.get(i + pos));
        }
        size -= pos;
        consumed += pos;
        pos = 0;
    }
    
    public int getAvaliable() {
        return size - pos;
    }
    
    public int getPosition() {
        return pos;
    }
    
    public boolean hasData() {
        return pos < size;
    }
    
    public int getConsumed() {
        return consumed;
    }
    
    public boolean hasRemaining() {
        return size < buffer.capacity();
    }
    
    public int getRemaining() {
        return buffer.capacity() - size;
    }
    
    public <T> T readValueAsBuffer(Function<ByteBuffer, T> func) {
        var buf = buffer.duplicate();
        buf.position(pos);
        buf.limit(size);
        var value = func.apply(buf);
        if (value != null) {
            var readLen = buf.position();
            pos += readLen;
        }
        return value;
    }
    
    public <T> T consumeValueAsBuffer(Function<ByteBuffer, T> func) {
        var value = readValueAsBuffer(func);
        if (value != null) {
            consume();
        }
        return value;
    }
    
    public int read(ByteBuffer destBuffer) {
        return read(destBuffer, destBuffer.remaining());
    }
    
    public int read(ByteBuffer destBuffer, int maxLen) {
        var readLen = Math.min(size - pos, maxLen);
        buffer.position(pos);
        buffer.limit(pos + readLen);
        destBuffer.put(buffer);
        buffer.position(0);
        buffer.limit(buffer.capacity());
        pos += readLen;
        return readLen;
    }
    
    public int read(byte[] data) {
        return read(data, 0, data.length);
    }
    
    public int read(byte[] data, int off, int len) {
        var readLen = Math.min(size - pos, len);
        buffer.position(pos);
        buffer.limit(pos + readLen);
        buffer.get(data);
        buffer.position(0);
        buffer.limit(buffer.capacity());
        pos += readLen;
        return readLen;
    }
    
    public void mark() {
        mark = pos;
    }
    
    public void rewind() {
        pos = mark;
    }
    
    public void rollback() {
        pos = 0;
    }
    
    public void rollback(int len) {
        if (pos > len) {
            throw new IllegalArgumentException("");
        }
        pos -= len;
    }
}
